/* Copyright (C) 2008  Romolo Camplani, Marco Fortunato, Marco Marelli, Guido Rota, Fabio A. Schreiber et al.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.dei.perla.parser.expressions;

/**
 * Costante di tipo INTEGER
 */
public class ConstantInteger extends ConstantBuiltin {

	// Valore della costante
	private int integerValue;	
	
	
	/**
	 *  Crea una costante impostata al valore specificato
	 */
	public ConstantInteger(int parValue) {
		this.initialize(parValue);
	}
	
	
	/**
	 * Crea una costante impostata al valore di default
	 */
	public ConstantInteger() {
		this.initialize(0);
	}
	
	
	/**
	 * Crea una costante a partire dalla stringa passata 
	 */
	public ConstantInteger(String parStringValue) throws ConstantCreationException {
		
		try {
			if (parStringValue.startsWith("0x") || parStringValue.startsWith("0X")) {
				this.initialize(Integer.parseInt(parStringValue.substring(2),16));
			}
			else {
				this.initialize(Integer.parseInt(parStringValue));
			}
		}
		catch (NumberFormatException parException) {
			throw new ConstantCreationException();
		}
	}
	
	
	/**
	 * Inizializza la costante	
	 */
	private void initialize(int parValue){	
		this.integerValue = parValue;
	}
	
	
	/**
	 * Casta il valore della costante a INTEGER 
	 */
	public int getValueInt() {
		return this.integerValue; 
	}
	
	/**
	 * Casta il valore della costante a FLOAT
	 */
	public double getValueFloat() {
		return (double)this.integerValue; 
	}
	
	/**
	 * Restituisce un oggetto Constant contenente il risultato dell'addizione
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public Constant addition(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class)
			return new ConstantInteger(this.integerValue + parConst.getValueInt());
		else if (parConst.getClass() == ConstantFloat.class)
			return new ConstantFloat((double)this.integerValue + parConst.getValueFloat());
		else if (parConst.isUserDefined())
			return parConst.additionInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else
			return super.addition(parConst);
	}
	
	public static Class<? extends Object> additionResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantFloat.class)
			return ConstantFloat.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("additionInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation + not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation + not supported with " + parConst.toString());
	}
	
	/**
	 * Restituisce un oggetto Constant contenente il risultato della sottrazione
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public Constant subtraction(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class)
			return new ConstantInteger(this.integerValue - parConst.getValueInt());
		else if (parConst.getClass() == ConstantFloat.class)
			return new ConstantFloat((double)this.integerValue - parConst.getValueFloat());
		else if (parConst.isUserDefined())
			return parConst.subtractionInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else
			return super.subtraction(parConst);
	}
	
	public static Class<? extends Object> subtractionResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantFloat.class)
			return ConstantFloat.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("subtractionInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation - not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation - not supported with " + parConst.toString());
	}
	
	/**
	 * Restituisce un oggetto Constant contenente il risultato della moltiplicazione
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public Constant multiplication(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class)
			return new ConstantInteger(this.integerValue * parConst.getValueInt());
		else if (parConst.getClass() == ConstantFloat.class)
			return new ConstantFloat((double)this.integerValue * parConst.getValueFloat());
		else if (parConst.isUserDefined())
			return parConst.multiplicationInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else
			return super.multiplication(parConst);
	}
	
	public static Class<? extends Object> multiplicationResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantFloat.class)
			return ConstantFloat.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("multiplicationInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation * not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation * not supported with " + parConst.toString());
	}
	
	/**
	 * Restituisce un oggetto Constant contenente il risultato della divisione
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 * @throws DivisionByZeroException 
	 */
	public Constant division(Constant parConst) throws OperationNotSupportedException, ConstantCastException, DivisionByZeroException {
		//aggiunta eccezione nel caso sia diviso 0;
		
		if ((parConst.getClass() == ConstantInteger.class) || (parConst.getClass() == ConstantFloat.class)) {
			try {
				if ((parConst.getValueFloat()==0) || (parConst.getValueInt()==0)) throw new DivisionByZeroException("A division by zero has been attempted!");
				return new ConstantFloat((double)this.integerValue / parConst.getValueFloat());
			} catch (Exception e) {
				throw new DivisionByZeroException("A division by zero has been attempted!");
			}
		}
		else if (parConst.isUserDefined())
			return parConst.divisionInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else
			return super.division(parConst);
	}
	
	public static Class<? extends Object> divisionResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if ((parConst== ConstantInteger.class) || (parConst == ConstantFloat.class))
			return ConstantFloat.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("divisionInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation / not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation / not supported with " + parConst.toString());
	}
	
	/**
	 * Restituisce un oggetto ConstantLogic contenente il risultato del confornto di uguaglianza
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public ConstantLogic isEqual(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			if (this.integerValue == parConst.getValueInt()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.getClass() == ConstantFloat.class) {
			if ((double)this.integerValue == parConst.getValueFloat()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.isUserDefined())
			return parConst.isEqual(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantLogic(LogicValue.UNKNOWN);
		else
			return super.isEqual(parConst);
	}
	
	/**
	 * Restituisce un oggetto ConstantLogic contenente il risultato del confornto di disuguaglianza
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public ConstantLogic isUnequal(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			if (this.integerValue != parConst.getValueInt()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.getClass() == ConstantFloat.class) {
			if ((double)this.integerValue != parConst.getValueFloat()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.isUserDefined())
			return parConst.isUnequal(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantLogic(LogicValue.UNKNOWN);
		else
			return super.isUnequal(parConst);
	}
	
	/**
	 * Restituisce un oggetto ConstantLogic contenente il risultato del confornto di maggiorita'
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public ConstantLogic isGreater(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			if (this.integerValue >= parConst.getValueInt()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.getClass() == ConstantFloat.class) {
			if ((double)this.integerValue >= parConst.getValueFloat()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.isUserDefined())
			return parConst.isGreaterInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantLogic(LogicValue.UNKNOWN);
		else
			return super.isGreater(parConst);
	}
	
	/**
	 * Restituisce un oggetto ConstantLogic contenente il risultato del confornto di stretta maggiorita' (uguaglianza non inclusa)
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public ConstantLogic isStrictlyGreater(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			if (this.integerValue > parConst.getValueInt()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.getClass() == ConstantFloat.class) {
			if ((double)this.integerValue > parConst.getValueFloat()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.isUserDefined())
			return parConst.isStrictlyGreaterInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantLogic(LogicValue.UNKNOWN);
		else
			return super.isStrictlyGreater(parConst);
	}
	
	/**
	 * Restituisce un oggetto ConstantLogic contenente il risultato del confornto di minorit�
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public ConstantLogic isMinor(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			if (this.integerValue <= parConst.getValueInt()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.getClass() == ConstantFloat.class) {
			if ((double)this.integerValue <= parConst.getValueFloat()) 
				return new ConstantLogic(LogicValue.TRUE);
			else 
				return new ConstantLogic(LogicValue.FALSE);
		}	
		else if (parConst.isUserDefined())
			return parConst.isMinorInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantLogic(LogicValue.UNKNOWN);
		else
			return super.isMinor(parConst);
	}
	
	/**
	 * Restituisce un oggetto ConstantLogic contenente il risultato del confornto di stretta minorita' (uguaglianza non inclusa)
	 * tra la costante stessa e l'oggetto Costant passato come parametro
	 * @throws ConstantCastException 
	 */
	public ConstantLogic isStrictlyMinor(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			if (this.integerValue < parConst.getValueInt()) return new ConstantLogic(LogicValue.TRUE);
			else return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.getClass() == ConstantFloat.class) {
			if ((double)this.integerValue < parConst.getValueFloat()) return new ConstantLogic(LogicValue.TRUE);
			else return new ConstantLogic(LogicValue.FALSE);
		}
		else if (parConst.isUserDefined())
			return parConst.isStrictlyMinorInverse(this);
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantLogic(LogicValue.UNKNOWN);
		else
			return super.isStrictlyMinor(parConst);
	}
	
	public ConstantLogic isBetween(Constant parConstBegin, Constant parConstEnd) throws OperationNotSupportedException, ConstantCastException {
		if (((parConstBegin.getClass() == ConstantInteger.class) || (parConstBegin.getClass() == ConstantFloat.class)) && ((parConstEnd.getClass() == ConstantInteger.class) || (parConstEnd.getClass() == ConstantFloat.class))) {
			if((this.isGreater(parConstBegin).getValueLogic() == LogicValue.TRUE) && (this.isMinor(parConstEnd).getValueLogic()  == LogicValue.TRUE))
				return new ConstantLogic(LogicValue.TRUE);
			else
				return new ConstantLogic(LogicValue.FALSE);
		}
		else if ((parConstBegin == null) || (parConstEnd == null))
			return new ConstantLogic(LogicValue.UNKNOWN);
		else
			return super.isBetween(parConstBegin, parConstEnd);
	}
	
	public Constant bitwiseAND(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			return new ConstantInteger(this.integerValue & parConst.getValueInt());
		}
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else if (parConst.isUserDefined()) {
			return parConst.bitwiseANDInverse(this);
		}
		else
			return super.bitwiseAND(parConst);
	}
	
	//TODO sistemare l'eccezione che tira questa classe perch� Exception e basta nun va bene
	public static Class<? extends Object> bitwiseANDResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("bitwiseANDInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation & not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation & not supported with " + parConst.toString());
	}
	
	public Constant bitwiseOR(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			return new ConstantInteger(this.integerValue | parConst.getValueInt());
		}
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else if (parConst.isUserDefined()) {
			return parConst.bitwiseORInverse(this);
		}
		else
			return super.bitwiseOR(parConst);
	}
	
	public static Class<? extends Object> bitwiseORResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("bitwiseORInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation | not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation | not supported with " + parConst.toString());
	}
	
	public Constant bitwiseXOR(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			return new ConstantInteger(this.integerValue ^ parConst.getValueInt());
		}
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else if (parConst.isUserDefined()) {
			return parConst.bitwiseXORInverse(this);
		}
		else
			return super.bitwiseXOR(parConst);
	}
	
	public static Class<? extends Object> bitwiseXORResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("bitwiseXORInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation ^ not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation ^ not supported with " + parConst.toString());
	}
	
	public Constant bitwiseNOT() throws OperationNotSupportedException {
		return new ConstantInteger(~this.integerValue);
	}
	
	public static Class<? extends Object> bitwiseNOTResultType() throws OperationNotSupportedException {
		return ConstantInteger.class;
	}
	
	public Constant bitwiseRightShift(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			return new ConstantInteger(this.integerValue >> parConst.getValueInt());
		}
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else if (parConst.isUserDefined()) {
			return parConst.bitwiseRightShiftInverse(this);
		}
		else
			return super.bitwiseRightShift(parConst);
	}
	
	public static Class<? extends Object> bitwiseRightShiftResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("bitwiseRightShiftInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation >> not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation >> not supported with " + parConst.toString());
	}
	
	public Constant bitwiseLeftShift(Constant parConst) throws OperationNotSupportedException, ConstantCastException {
		if (parConst.getClass() == ConstantInteger.class) {
			return new ConstantInteger(this.integerValue << parConst.getValueInt());
		}
		else if (parConst.getClass() == ConstantNull.class)
			return new ConstantNull();
		else if (parConst.isUserDefined()) {
			return parConst.bitwiseLeftShiftInverse(this);
		}
		else
			return super.bitwiseLeftShift(parConst);
	}
	
	public static Class<? extends Object> bitwiseLeftShiftResultType(Class<? extends Constant> parConst) throws OperationNotSupportedException {
		if (parConst == ConstantInteger.class)
			return ConstantInteger.class;
		else if (parConst == ConstantNull.class)
			return ConstantNull.class;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return (Class<? extends Object>) parConst.getMethod("bitwiseLeftShiftInverseResultType", Class.class).invoke(null, ConstantInteger.class);
			} catch (Exception e) {
				throw new OperationNotSupportedException("Operation << not supported with " + parConst.toString());
			}
		}
		else
			throw new OperationNotSupportedException("Operation << not supported with " + parConst.toString());
	}
	
	public Constant sign() throws OperationNotSupportedException {
		return new ConstantInteger(-(this.integerValue));
	}
	
	public static Class<? extends Object> signResultType() throws OperationNotSupportedException {
		return ConstantInteger.class;
	}
	/**
	 * Dato un tipo Constant il metodo ritorna true se ConstantInteger supporta le operazioni
	 * di uguaglianza e disuguaglianza con la classe Constant passata come parametro. In caso contrario ritorna false.
	 * @param parConst
	 * @return
	 */
	public static boolean isEquallyComparable(Class<? extends Constant> parConst) {
		if ((parConst== ConstantInteger.class) || (parConst == ConstantFloat.class) || (parConst == ConstantNull.class))
			return true;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return ((Boolean) parConst.getMethod("isEquallyComparable", Class.class).invoke(null, ConstantInteger.class)).booleanValue();
			} catch (Exception e) {
				return false;
			}
		}
		else
			return false;
	}
	
	/**
	 * Dato un tipo Constant il metodo ritorna true se ConstantInteger supporta le operazioni
	 * di uguaglianza, disuguaglianza, maggiorit� e minorit� con la classe Constant passata come parametro. In caso contrario ritorna false.
	 * @param parConst
	 * @return
	 */
	public static boolean isFullyComparable(Class<? extends Constant> parConst) {
		if ((parConst== ConstantInteger.class) || (parConst == ConstantFloat.class) || (parConst == ConstantNull.class))
			return true;
		else if (parConst.getSuperclass() == ConstantUserDefined.class) {
			try {
				return ((Boolean) parConst.getMethod("isFullyComparable", Class.class).invoke(null, ConstantInteger.class)).booleanValue();
			} catch (Exception e) {
				return false;
			}
		}
		else
			return false;
	}
	
	/**
	 * Restituisce una stringa rappresentante il valore della Costante
	 */
	public String toString() {
		return String.valueOf(this.integerValue);
	}
}